home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1995 April / Internet Tools.iso / infoserv / gopher / Unix / GopherTools / whatsnewd.Z / whatsnewd
Encoding:
Text File  |  1994-05-03  |  26.1 KB  |  871 lines

  1. Version 0.43 1/4/94
  2.  
  3. I. Here is the code for and description of "whatsnewd" and "findwhatsnew":
  4.  
  5. (Connect your Gopher to "gopher.eff.org", port "5070" to play with it.)
  6.  
  7. What it does:
  8.  
  9. When a user gives a date (e.g. "1 day ago") or enters a dated bookmark
  10. (generated by a previous query), he or she gets a gopher menu of the
  11. gopher items that are new or changed since that date.
  12.  
  13. The generated bookmarks allow a user to see just those items that
  14. have been created or changed since his or her last query.
  15.  
  16. I won't have time to work any more on this for a while. But here is
  17. the code, "as is". It consists of two programs:
  18.    findwhatsnew - which updates a database of gopher items
  19.    whatsnewd    - a gopher protocol server
  20. Both are written in Perl.
  21.  
  22. You're are encouraged to add to or modify this code.
  23. (And to improve this documentation.)
  24.  
  25. [Joaquim Baptista, px@fct.unl.pt, has done this creating "traveller" a
  26. hybrid of "whatsnew" and "veronica" with other new features. To try
  27. it, do "gopher -ptravel gopher.fct.unl.pt 4320". As of today,
  28. the software is in boombox.micro.umn.edu:pub/gopher/incoming.]
  29.  
  30. -- Carl Kadie (kadie@eff.org) 09/21/93
  31.  
  32. II. ============== About findwhatsnew ==============
  33.  
  34. USAGE findwhatsnew NAME PATH SERVER PORT DEPTH
  35. OR    findwhatsnew NAME
  36.  
  37. For example:
  38.       findwhatsnew caf "1/academic" gopher.eff.org 70 3
  39.       findwhatsnew caf
  40.  
  41. NAME can include a directory path. For example:
  42.  
  43.       findwhatsnew /net/kragar/serv/gopher/whatsnewd/data/caf "1/academic" gopher.eff.org 70 3
  44.  
  45. Notes:
  46.  
  47. 1. You will eventually need to create a whatsnewd directory.
  48. Ours is a subdirectory of our main "gopher" directory.
  49. It should have three subdirectories:
  50.       bin data logs
  51. "data" is where "whatsnewd" expects to see the database
  52. that "findwhatsnew" creates.
  53.  
  54. 2. Run "findwhatsnew" first with a name and all the parameters
  55.  
  56.    findwhatsnew NAME PATH SERVER PORT DEPTH
  57.  
  58. After that, you need not give the parameters (unless you want to
  59. change them). e.g.
  60.  
  61.     findwhatsnew NAME
  62.  
  63. will usually be enough.
  64.  
  65. 3. The parameters
  66.  
  67.    NAME -- will become the short name used to make files.
  68.    PATH -- a gopher path
  69.    SERVER -- aka HOST
  70.    PORT -- usually 70
  71.    DEPTH -- start with 1 or 2 and slowly increase until
  72.           you are sure you have your kill and keep files the way you want
  73.  
  74. 4. Files (only NAME.kill is user created)
  75.  
  76.     NAME.times - text file. Each line is a time stamp and
  77.                       bookmark. Here is an example line:
  78.  
  79.              19921208075043<tab>0cafv02n56<tab>0/academic/news/cafv02n56<tab>gopher.eff.org<tab>70
  80.  
  81.               Dates are of the form YYYYMMDDHHMMSS and are GMT.
  82.  
  83.     NAME.bak - the old version of NAME.times
  84.         NAME.tmp - temporary version of NAME.times
  85.             (NAME.times is replaced by NAME.tmp at the very end of processing.)
  86.     NAME.kill - kills and keeps certain paths. Examples are enclosed.
  87.     NAME.save - the parameters for the program.
  88.         NAME.tree - the explored gopher tree (nice and indented)
  89.             This is very handy for tuning the NAME.kill file.
  90.  
  91. 5. Kill file:
  92.  
  93. A kill file is used to stop whatsnewd from searching places you don't
  94. what it too look.
  95.  
  96. Comment lines in a kill file start with a "#". Blanks lines are OK,
  97. too.
  98.  
  99. The four commands are:
  100.  
  101. kill PATTERN
  102. keep PATTERN
  103. kill-subs-of PATTERN
  104. keep PATTERN
  105.  
  106. Where PATTERN is a Perl regular expression. A kill file
  107. may contain many instances of each type of command.
  108.  
  109. Every time an item is found, these patterns are consulted. If the item
  110. matches at least one "keep" pattern and doesn't match any "kill"
  111. patterns, it is included in the database.  If an item matches at least
  112. one "keeps-subs-of" pattern and doesn't machine any
  113. "kill-subs-of" patterns, its subitems (if any) will be explored.
  114.  
  115. For the purpose of this pattern matching an item is described as
  116. a string of the form:
  117.  
  118. "
  119. Name0=NAME0VALUE
  120. Port0=PORT0VALUE
  121. Path0=PATH0VALUE
  122. Host0=HOST0VALUE
  123. Name=NAMEVALUE
  124. Type=TYPEVALUE
  125. Port=PORTVALUE
  126. Path=PATHVALUE
  127. Host=HOSTVALUE
  128. "
  129.  
  130. Where all the fields ending in 0 have values for
  131. the item's parent. And the non-0-ending fields have values
  132. for the item.
  133.  
  134. 6. Example kill files:
  135.  
  136. ============== caf.kill ==============
  137. # Stops it from exploring "whatsnew" stuff
  138. kill-subs-of \nPort0=5070\n
  139. # Stops it from exploring articles of the Campus Newspapers
  140. kill-subs-of \nPath=1/academic/newspapers
  141. # Don't look at subdirectories of items whose path starts "m/"
  142. kill-subs-of \nPath=m/
  143. # Don't follow gopher link for library material back to top of CAF
  144. kill \nPath=1/academic/library/academic\n
  145. # Keep only those items reached from gopher.eff.org 
  146. keep \nHost0=gopher.eff.org\n
  147. keep \nHost0=kragar.eff.org\n
  148.  
  149. ============== cso.kill ===================
  150. # Don't go more than one step about from a uiuc.edu machine.
  151. keep \nHost0=.*\.uiuc\.edu\n
  152.  
  153. # Don't go from a department machine back to the main campus gopher machine
  154. kill \nType=1\nPort=70\nPath=(1/)?\nHost=harpoon\.cso\.uiuc\.edu\n
  155. kill \nType=1\nPort=70\nPath=(1/)?\nHost=gopher\.(cso\.)?uiuc\.edu\n
  156. kill \nHost0=s\.psych\.uiuc\.edu\n(.|\n)*\nHost=harpoon\.cso.\uiuc\.edu\n
  157. kill \nHost0=cyberdyne\.ece\.uiuc\.edu\n(.|\n)*\nHost=gopher.\uiuc\.edu\n
  158. kill \nHost0=s\.psych\.uiuc\.edu\n(.|\n)*\nHost=gopher\.uiuc\.edu\n
  159.  
  160. # Kill an experimental branch
  161. kill-subs-of \nPath=1/Information about Gopher/exp\n
  162.  
  163. # Stops it from exploring "whatsnew" bookmarks
  164. kill \nPath=.* gophergmt\n
  165.  
  166. # Don't look at subdirectories of items whose path starts "m/"
  167. kill-subs-of \nPath=m/
  168.  
  169. kill-subs-of \nPath=1/FTP\n
  170. kill-subs-of \nPath=1/Phone Books\n
  171. kill-subs-of \nPath=1/UI/Timetables\n
  172.  
  173. # Don't explore the Placement Office
  174. kill \nPort=15000\n(.|\n)*\nHost=harpoon\.cso\.uiuc\.edu\n
  175. # Don't explore the newspaper
  176. kill \nPath=1/UI/DI\n
  177. kill-subs-of \nPath=1/News/Daily Illini Newspaper\n
  178. # Don't explore Student Employment
  179. kill-subs-of \nPath=1/UI/FinAid/Student Employment\n
  180.  
  181. kill-subs-of \nPath=1/Manuals/Unix\n
  182. kill \nPath=1/Manuals/SM/
  183. kill-subs-of \nPath=1/Forecasts\n
  184. kill-subs-of \nPath=1/Current Conditions\n
  185. kill \nPath=ftp:
  186. kill-subs-of \nPort=70\nPath=1/Forecast\nHost=wx\.atmos\.uiuc\.edu\n
  187. kill-subs-of \nPort=70\nPath=1/Illinois\nHost=wx\.atmos\.uiuc\.edu\n
  188. kill-subs-of \nPort=70\nPath=1/Images\nHost=wx\.atmos\.uiuc\.edu\n
  189. kill-subs-of \nPort=70\nPath=1/Surface\nHost=wx\.atmos\.uiuc\.edu\n
  190. kill-subs-of \nPort=70\nPath=1/Upper\nHost=wx\.atmos\.uiuc\.edu\n
  191. kill-subs-of \nPort=70\nPath=1/Severe\nHost=wx\.atmos\.uiuc\.edu\n
  192. kill-subs-of \nPort=70\nPath=1/Servers\nHost=wx\.atmos\.uiuc\.edu\n
  193.  
  194. # Don't look at individual articles in _Inside Illinois_
  195. #  Kill both ways to get to the articles
  196. kill-subs-of \nPath0=1/UI/II\n
  197. kill-subs-of \nPath0=1/News/Inside Illinois, the Faculty-Staff Newspaper\n
  198.  
  199. # Don't look at individual books
  200. kill-subs-of \nPath0=1/library\n
  201.  
  202. #Kill specific manuals and scripts
  203. kill-subs-of \nPath0=1/Documents\nHost0=wx\.atmos\.uiuc\.edu\n
  204.  
  205.  
  206. =======================================================
  207.  
  208. 7. What exactly the program does
  209.  
  210. It reads the old database of time stamps and bookmarks. If it sees
  211. bookmarks, identical except from the time stamp, it uses the older
  212. time stamp.
  213.  
  214. It explores the gopher tree within the bounderies set by the depth
  215. bound and the kill file. It remembers the Name/Port/Path/Host of the
  216. items that it sees. Duplicates are not explored.
  217.  
  218. If it finds an item that is mentioned in the old database,
  219. it puts the item in the new database with the old time stamp.
  220.  
  221. If it finds an item that is not mentioned in the old database,
  222. it puts the item in the new database, stamped with the current time.
  223.  
  224. Items from the old database that are not found, are put in the new
  225. database anyway. They are marked "Couldn't connect: ".  From time to
  226. time, you may want to delete these items manually from the database
  227. (*.times file).
  228.  
  229. III. ============== About whatsnewd ==============
  230.  
  231. USAGE: whatsnewd -lLOGFILE Datadir
  232.  
  233. "whatsnewd" is a gopher-protocol server. 
  234.  
  235. If a user gives a date (e.g. "1 day ago") or enters a dated bookmark
  236. (generated by a previous query), he or she gets a gopher menu of the
  237. gopher items that are new or changed since that date.
  238.  
  239. Its input is:
  240.    DATADIR  - a directory containing *.times and *.save files
  241.                   as created by "findwhatsnew"
  242.    LOGFILE  - a log file
  243.    stdin - a command (i.e. gopher path). For example:
  244.       1\      : create a menu for every database you know about
  245.       1\NAME  : create a menu for the NAME.times database
  246.       0\NAME : create information about the NAME.times database
  247.       7\NAME<tab>STRING : Answer a query about NAME
  248.       1\NAME\STRING : same as 7\NAME<tab>STRING
  249.  
  250. Its output is:
  251.     stdout - gopher menus or text items
  252.  
  253. 2. You can play with the program before installing it as a daemon.
  254. Just run it (don't forget parameters) and type into standard input.
  255.  
  256. 3. Ultimately it should be installed as a network daemon. 
  257. (My sysadmin, Chris Davis, ckd@eff.org, did this for me).
  258.  
  259. Port 5070 is suggested, but any port should work. Here is the line
  260. from gopher.eff.org's "etc/inetd":
  261.  
  262. gwn     stream  tcp     nowait  gopher  /serv/gopher/whatsnewd/bin/whatsnewd whatsnewd -l/serv/gopher/whatsnewd/logs/whatsnewd.log /serv/gopher/whatsnewd/data
  263.  
  264. And here is the line from gopher.eff.org's "services":
  265.  
  266. services:gwn             5070/tcp        whatsnewd       # gopher what's new
  267.  
  268. Here is where we've but it's files:
  269.   The program is:
  270.        /serv/gopher/whatsnewd/bin/whatsnewd
  271.    The log file is:
  272.       /serv/gopher/whatsnewd/logs/whatsnewd.log
  273.    And the *.times and *.save files live in:
  274.      /serv/gopher/whatsnewd/data
  275.  
  276. IV.  The Code: ========== findwhatsnew =================
  277. #!/usr/bin/perl
  278. # !/local/all/perl
  279.  
  280.  
  281. # History
  282. #  July 24, 1993   - mark old saved items to make them easier to dump
  283. #                     if two identical items are in the *.times file,
  284. #                                  use the older date
  285. #  July 7, 1993    - changed a bit to make work with new minor release of Perl
  286. #  Jan 1, 1992      - save old items, even if not found
  287. #  Dec 9, 1992        - output *.tree file
  288. #  Dec 3, 1992      - change to findwhatsnew
  289. #  Nov 23, 1992 cmk - initial development of gopherdiff
  290.  
  291. # Bugs: if gopher can supply time, that should be used instead.
  292.  
  293. $sort = "/bin/sort";
  294. $rm = "/usr/bin/rm";
  295.  
  296. $| = 1;
  297.  
  298. ($cursec,$curmin,$curhour,$curmday,$curmon,$curyear,
  299.  $curwday,$curyday,$curisdst)
  300.  = gmtime(time);
  301. $curmon++;
  302. if ($curyear > 90) {$curyear = $curyear + 1900;}
  303. elsif ($curyear < 20) {$curyear = $curyear + 2000;}
  304. else {die "Sorry, limited to years between 1990 and 2020";}
  305.  
  306. $curdate = sprintf("%.4d%.2d%.2d%.2d%.2d%.2d",$curyear,$curmon,$curmday,$curhour,$curmin,$cursec);
  307.  
  308. $cannotconnect = "Couldn't connect: ";
  309. ($gopherdir,$name) = @ARGV[0] =~ /(.*)([^\/]*)/;
  310. #print STDERR "< $gopherdir $name >\n";
  311.  
  312. $bakfile = "$gopherdir$name.bak";
  313. $oldfile = "$gopherdir$name.times";
  314. if (open(OLD,"<$oldfile")) {
  315.     while (<OLD>) {
  316.         ($date2,$tname2,$rest2) = /^([^\t]*)\t([^\t]*)\t(.*).$/;
  317.         #print STDERR
  318.                  #  "date2=$date2\n\ttname2=$tname2\n\trest2=$rest2\n";
  319.         $rest2 =~ s/^($cannotconnect)+//;
  320.         $tname2 =~ s/^($cannotconnect)+//;
  321.         #print STDERR
  322.                  #  "date2=$date2\n\ttname2=$tname2\n\trest2=$rest2\n";
  323.             $rem2 = @REMEM{$rest2};
  324.                 #print "before:$rest2->@REMEM{$rest2}\n";
  325.                  # if new "tag" then just remember
  326.             if ($rem2 eq "") {
  327.                  @REMEM{$rest2} = "$date2\t$tname2";
  328.                 } else { # if have seen "tag" before keep older tag
  329.         ($date22,$tname22) = split("\t",$rem2);
  330.                      #print "$date2<$date22\n";
  331.                    if ($date2<$date22) {
  332.                       @REMEM{$rest2} = "$date2\t$tname2";
  333.                      }
  334.                 }
  335.                 #print "after:$rest2->@REMEM{$rest2}\n";
  336.     }
  337.     close(OLD);
  338.     }
  339. #print STDERR "OLD is loaded\n";
  340.  
  341. $newfile = "$gopherdir$name.tmp";
  342. open(NEW,">$newfile")||die("Cannot open $newfile");
  343. $oldhandle = select(NEW); $| = 1; select ($oldhandle);
  344.  
  345. $treefile = "$gopherdir$name.tree";
  346. open(TREE,">$treefile")||die("Cannot open $treefile");
  347. $oldhandle = select(TREE); $| = 1; select ($oldhandle);
  348.  
  349. @kills = &LOAD_KILL_FILE($gopherdir,$name);
  350.  
  351. $save = "$gopherdir$name.save";
  352. if ($#ARGV == 4) {
  353.     open (SAVE,">$save");
  354.     print SAVE join("\n",@ARGV);
  355.     print SAVE "\n";
  356.     close(SAVE);
  357.   } elsif ($#ARGV == 0) {
  358.     @ARGV=();
  359.     open (SAVE,"<$save");
  360.     while(<SAVE>){chop; push(@ARGV,$_);}
  361.     close(SAVE);
  362.    } else {
  363.      print "USAGE findwhatsnew NAME PATH SERVER PORT DEPTH\n";
  364.      print "OR    findwhatsnew NAME\n";
  365.      exit;
  366.        }
  367.   $oridepth = @ARGV[4];
  368.   shift;
  369.   $start_gopher = "gopher -p \"@ARGV[0]\" @ARGV[1] @ARGV[2]";
  370.   push(@ARGV,"");
  371.   &RECUR(@ARGV);
  372.   # output old items, not seen this time
  373.   foreach $key9 (keys(%REMEM)) {
  374.      $rem9 = @REMEM{$key9};
  375.     if ($rem9 ne "1") {
  376.      $rem9 =~ s/\t/\t$cannotconnect/;
  377.      print NEW "$rem9\t$key9\r\n";
  378.       }
  379.      }
  380.   unlink($bakfile) if -e $bakfile;
  381.   rename($oldfile,$bakfile);
  382.   close;
  383.   exec("$sort < $newfile > $oldfile;$rm $newfile");
  384.  
  385. sub RECUR {
  386. local($path,$server,$port,$depth) = @_;
  387. local($newtype,$newline,$name1,$path1,$server1,$port1);
  388. local($dir,$rem,$c);
  389.  
  390. #print STDERR "<< $server,$port,$path,$depth >>\n";
  391.  
  392. $dir = &CLIENT($server,$port,"$path\r\n");
  393.  
  394. #print STDERR "<2> $dir\n";
  395. foreach $_ (split("\n",$dir)) {
  396.     #print STDERR $_;
  397.     $item = $_;
  398.     ($type1,$name1,$rest1,$path1,$server1,$port1) 
  399.            = /^(.)([^\t]*)\t(([^\t]*)\t([^\t]*)\t([^\t]*).*)$/;
  400.          $tname1= "$type1$name1";
  401.     #print STDERR ">3>$type1,$name1,$rest1,$path1,$server1,$port1\n";
  402.     #print $rest1;
  403.     $rem = @REMEM{$rest1};
  404.     ($remdate,$remname) = split("\t",$rem);
  405.     #print "<rem< $rem, $remdate, $remname >>\n";
  406.     $killlong ="\nName0=$name\nPort0=$port1\nPath0=$path\nHost0=$server\nName=$name1\nType=$type1\nPort=$port1\nPath=$path1\nHost=$server1\n";
  407.     study($killong);
  408.     $*=1;
  409.     $k1 = !(@kills{'kill'} && ($killlong =~ /@kills{'kill'}/));
  410.     $k2 = (!@kills{'keep'} || ($killlong =~ /@kills{'keep'}/));
  411.     #print STDERR ">4>Killlong=$killlong\n";
  412.      #print STDERR ">5>don't kill=$k1. keep=$k2 rem=$rem\n";
  413.     if ($k1 && $k2 && $rem != 1) {
  414.             $*=0;
  415.         #print STDERR ">6>\n";
  416.          if ($remname ne $tname1){
  417.         print NEW "$curdate\t$item\r\n";
  418.         } else {
  419.          print NEW "$remdate\t$item\r\n";
  420.         }
  421.         #print STDERR ">7>\n";
  422.         $indent = ($oridepth - $depth) * 3 + 1;
  423.         printf(TREE "%${indent}d:%s\n",$depth,$item);
  424.         @REMEM{$rest1} = 1;
  425.         #print STDERR ">8>\n";
  426.        if ($type1 ==1) {
  427.         $k1s = !(@kills{'kill-subs-of'} &&
  428.                              ($killlong =~ /@kills{'kill-subs-of'}/));
  429.         $k2s = (!@kills{'keep'-subs-of} || 
  430.                              ($killlong =~ /@kills{'keep-sub-of'}/));
  431.                 #print "don't kill sub=$k1s. keep sub=$k2s\n";
  432.         #print STDERR ">9>\n";
  433.         if ($k1s && $k2s) {
  434.             if ($depth >0) {
  435.              do &RECUR($path1,$server1,$port1,$depth-1);
  436.             } else {
  437.                #print STDERR "Depth bound reached. Will not explore:\n";
  438.                #print STDERR "$name1\n    $path1    $server1    $port1\n\n";
  439.             }}
  440.         #print STDERR ">10>\n";
  441.         }}
  442.         #print STDERR ">11>\n";
  443.        $*=0;
  444. }
  445. 1;
  446. }
  447.  
  448.  
  449. # Based on "client" on page 344 of _Programming Perl_ by Wall and Schwartz
  450.  
  451. sub CLIENT {
  452.  
  453. local($them,$port,$input) = @_;
  454. local($client);
  455.  
  456. if ($them eq "error.host") { return("");}
  457.  
  458. $port = 2345 unless $port;
  459. $them = 'localhost' unless $them;
  460.  
  461. $AF_INET =2;
  462. $SOCK_STREAM = 1;
  463.  
  464. $SIG{'INT'} = 'dokill';
  465.  
  466. $sockaddr = 'S n a4 x8';
  467.  
  468. chop($hostname = `hostname`);
  469.  
  470. ($name,$aliases,$proto) = getprotobyname('tcp');
  471. ($name,$aliases,$port) = getservbyname($port,'tcp')
  472.      unless $port =~ /^\d+$/;;
  473. ($name,$aliases,$type,$len,$thisaddr) = gethostbyname($hostname);
  474. ($name,$aliases,$type,$len,$thataddr) = gethostbyname($them);
  475.  
  476. $this = pack($sockaddr,$AF_INET, 0, $thisaddr);
  477. $that = pack($sockaddr,$AF_INET, $port, $thataddr);
  478.  
  479. # Make the socket filehandle
  480. socket(S, $AF_INET, $SOCK_STREAM, $proto)|| die $!;
  481.  
  482.  
  483. # Give the socket an address.
  484. bind(S, $this) || die $!;
  485.  
  486. # Call up the server.
  487. if (connect(S,$that)) {
  488.     #print "connect ok\n";
  489. }
  490. else {
  491.     #print STDERR "CANNOT CONNECT TO $them $port $input $!\n";
  492.     return("");
  493. }
  494.  
  495. # Set socket to be command buffered.
  496.  
  497. $oldhandle = select(S); $| = 1; select ($oldhandle);
  498.  
  499. #print STDERR $input;
  500. print S $input;
  501.  
  502. READ: while (<S>) {
  503.         #print STDERR;
  504.     chop;chop;
  505.     last READ if ($_ eq ".");
  506.     $client .= "$_\n";
  507.     }
  508. #print STDERR "done with client\n";
  509. return($client);
  510. }
  511.  
  512. sub LOAD_KILL_FILE {
  513.     local($gopherdir,$name) = @_;
  514.     local($killfile) = "$gopherdir$name.kill";
  515.     local(@kills) = ();
  516.     @kills{'kill'} = '0'; @kills{'kill-subs-of'} = '0';
  517.     @kills{'keep'} = '0'; @kills{'keep-subs-of'} = '0';
  518.     if (open(KILL,"<$killfile")) {
  519.     while (<KILL>) {
  520.         if (!/^\s*(#.*)?$/) {
  521.         /(\S+) (.*)/;
  522.         chop;
  523.         local($so_far) = @kills{$1};
  524.         if ($so_far eq '') {die "Kill file command $1 not recognized";}
  525.         if ($so_far eq '0') 
  526.             {@kills{$1} = '';} else {@kills{$1} .= '|';}
  527.         @kills{$1} .= $2;
  528.         #print "kills{$1} = @kills{$1}\n";
  529.         }}
  530.     }
  531.     return(@kills);
  532.     }
  533.  
  534. V. More code: ======= whatsnewd ==========
  535. #!/usr/bin/perl
  536.  
  537. # History
  538. #  Jan 4, 1993 -- allow "+" to be used in place of " " (for www compatiblity)
  539. #  July 29, 1993 - adding code to allow it to work with sequence numbers
  540. #                    (but sequence numbers aren't used)
  541. #  Dec 8, 1992 - fixed bug cause by limit and date interaction
  542. #  Dec 3, 1992  - Carl M. Kadie - initial development
  543.  
  544. # Bugs and Limitations:
  545. #       Does a linear, rather than binary search
  546. #       Only works with GMT and relative times, no local times
  547. #       The termcap gopher client will highlight words from search string
  548.  
  549. # Assumptions:
  550. #     Assumes item list is sorted.
  551. #            (this is used to find date for new bookmark)
  552.  
  553. # Possibilities:
  554. #      Could findwhatsnew could remember menu selection paths
  555. #      Could use sequence numbers rather than dates.
  556.  
  557. require "getopts.pl";
  558. &Getopts("l:");
  559.  
  560. if ($opt_l) {
  561.     $logfile = $opt_l;
  562. } else {
  563.     $logfile = "/dev/null";
  564. }
  565.  
  566.  
  567. if ($#ARGV == 0) {
  568.     ($whatsnewdir) = @ARGV;
  569.    } else {
  570.      print "USAGE: whatsnewd -lLOGFILE Datadir\n";
  571.      exit;
  572.        }
  573.  
  574. $host = `hostname`;
  575. $default_limit = 100;
  576. $oldext = "times";
  577. $saveext = "save";
  578. chop($host);
  579.  
  580. @seconds{'s'} = 1;
  581. @seconds{'m'} = @seconds{'s'} * 60;
  582. @seconds{'h'} = @seconds{'m'} * 60;
  583. @seconds{'d'} = @seconds{'h'} * 24;
  584. @seconds{'w'} = @seconds{'d'} * 7;
  585.  
  586. &SERVER($port);
  587. #&TESTSERVER($port);
  588.  
  589. sub TESTSERVER {
  590.     $input = <STDIN>;
  591.     chop($input);
  592.     open(NS,">del");
  593.     &WHATSNEW($input);
  594. }
  595.  
  596. sub SERVER {
  597.  
  598.     $sockaddr = 'S n a4 x8';
  599.     $mysockaddr = getsockname(STDIN);
  600.     $remsockaddr = getpeername(STDIN);
  601.     if ($mysockaddr) {
  602.         ($family,$myport,$myaddr) 
  603.                     = unpack($sockaddr,$mysockaddr);
  604.         ($remfamily,$remport,$remaddr) 
  605.                     = unpack($sockaddr,$remsockaddr);
  606.         ($theirname) = gethostbyaddr($remaddr,2);
  607.         } else {
  608.         $myport = 5070;
  609.         $theirname = "justtesting.debug";
  610.         }
  611.     $input = <STDIN>;
  612.     chop($input);
  613.     $input =~ s/\r$//; # remove trailing \r if any
  614.         $input =~ s/\+/ /g;  # change +'s to blanks
  615.         $input =~ s/\?/\t/g;  # change ?'s to tabs
  616.     #print $input;
  617.     &WHATSNEW($input);
  618. }
  619.  
  620. sub WHATSNEW {
  621.  
  622. ($path,$search) = split("\t",@_[0]);
  623. ($type,$short_name,$style) = split("/",$path);
  624. if ($type eq '') {$type = 1;}
  625. if (($type ==1) && ($short_name eq '' || $short_name eq '.')) {
  626.     opendir(DIR,$whatsnewdir) || die ($whatsnewdir,": ",$!);
  627.     @timefiles = grep(/\.$oldext$/,readdir(DIR));
  628.     foreach $timefile (@timefiles) {
  629.         $timefile =~ /(.*)\.$oldext$/;
  630.         local($short_name) = $1;
  631.         local($path1,$host1,$port1) = &GET_GOPHER_REF($1);
  632.         local($date) = &LASTDATE($short_name);
  633.         &PRINT_EXTRA($short_name,$path1,$host1,$port1,$date,0,"about,whatsnew,top");
  634.         }
  635.     closedir(DIR);
  636.     } else {
  637.    local($path1,$host1,$port1) = &GET_GOPHER_REF($short_name);
  638.  
  639.   if ($type == 0 && $style eq '') {
  640.     $curdate = &TIME2GOPHER(time,1);
  641.     open(OLD,"<$whatsnewdir/$short_name.$oldext") || die "$whatsnewdir/$short_name.$oldext: ",$!;
  642.     $_ = <OLD>;
  643.     # what if empty?
  644.     close(OLD);
  645.     ($oldestdate) = /^([^\t]*)\t/;
  646.     $recentdate = &LASTDATE($short_name);
  647.     &LOG_MESS("generated info on $short_name");
  648. print "About \"What's New in $short_name\":\r
  649. \r
  650. To search for items that have been created or changed\r
  651. since some date, give that date as the search string.\r
  652. \r
  653. Dates can be specified in two formats. For example:\r
  654.      1 day 35 min ago\r
  655. and\r
  656.      19921104040013 gophergmt\r
  657. \r
  658. In general the formats are:\r
  659.     {W w{weeks}} {D d{ays}} {H h{hours}} {M m{inutes}} {S s{econds}} ago\r
  660. and\r
  661.     YYYYMMDDHHMMSS gophergmt\r
  662. \r
  663. The last three items listed will be:\r
  664.     **About \"What's New in $short_name\"\r
  665.     **The top of the \"$short_name\"'\r
  666.     **Bookmark for future new \"$short_name\" items (...)\r
  667. \r
  668. If you save the bookmark now and \"enter\" it in the the future, it will\r
  669. display the items that have change between now until then.\r
  670. \r
  671. ==================Some Status Info ==========================\r
  672.    The current date is $curdate gophergmt. The date of the oldest\r
  673. $short_name item is $oldestdate gophergmt. The date of the most recent\r
  674. $short_name item is $recentdate gophergmt.\r
  675. .\r\n";
  676.     } elsif ($type == 0) {
  677. print "\r
  678. The default limit on the number of items returned is $default_limit.\r
  679. To change this, add, for example, \"30 limit\" or \"no limit\" to your\r
  680. search string.\r
  681. .\r\n";
  682.     } elsif ($type == 1 && $style eq "") {
  683.     $date = &LASTDATE($short_name);
  684.     &PRINT_EXTRA($short_name,$path1,$host1,$port1,$date,0,"about,whatsnew,top");
  685.     } elsif ($type == 1 || $type == 7){
  686.      #print "style=$style, search=$search\n";
  687.     if ($type == 1) {$search = $style;}
  688.     #print "style=$style, search=$search\n";
  689.     ($since,$limit) = &PROCESS_DATE($search);
  690.     #print "since=$since, limit=$limit\n";
  691.     $date = $since;
  692.     #$last = &LASTDATE($short_name);
  693.     open(OLD,"<$whatsnewdir/$short_name.$oldext") || die "$whatsnewdir/$short_name.$oldext: ",$!;
  694.     #print "limit=$limit\n";
  695.     local($ll) = $limit;
  696.     &LOG_MESS("searched $short_name with @_[0]");
  697.     LOOP5: while(<OLD>) {
  698.             if (/^[0-9]{14,14}\t/) { # old format
  699.         ($date,$rest) = /^([^\t]*)\t(.*)/;
  700.                 } else {
  701.         ($seq,$date,$rest) = /^([^\t]*)\t([^\t]*)\t(.*)/;
  702.                  }
  703.         # print "$date > $search ? \n";
  704.         if ($date >$since) {
  705.             $ll --;
  706.             #print "ll=$ll\n";
  707.             last LOOP5 if $ll == -1;
  708.              print "$rest\n";
  709.                     }
  710.         }
  711.     if ($ll == -1) {
  712.     print "0**Limit of $limit Reached. Select for more information    0/$short_name/limit    $host    $myport\r\n";
  713.     $date = &LASTDATE($short_name);
  714.     }
  715.     #print "date=$date\n";
  716.     if ($type==1) {
  717.     &PRINT_EXTRA($short_name,$path1,$host1,$port1,$date,1,"about,whatsnew,top,bookmark");
  718.     } else {
  719.     &PRINT_EXTRA($short_name,$path1,$host1,$port1,$date,1,"about,top,bookmark");
  720.     print ".\r\n";}
  721.         }}
  722.  
  723. #    Binary search -- coding not finished
  724. #    $filelen = (stat(OLD))[7];
  725. #    $lo = 0;
  726. #    $hi = $filelen - 3;
  727. #    NARROW: while (1) {
  728. #        last NARROW if $lo == $hi;
  729. #        $mid = int(($lo + $hi) /2);
  730. #        ($date1,$date2) = &RETURN_TIME($mid);
  731. #        if ($date1 )}
  732.  
  733. sub RETURN_TIME {
  734.     local($pos) = @_;
  735.     local($c,$date);
  736.     BACK: for (; $pos >= 0; $pos--){
  737.         seek(OLD,$pos,0);
  738.         read(OLD,$c,1);
  739.                 if ($c eq "\n") {
  740.             $pos++;
  741.             last BACK;
  742.             }
  743.         }
  744.      seek(OLD,$pos,0);
  745.     $_ = <OLD>;
  746.     ($date1) = /([0-9]{14,14})\t/;
  747.     return($date1);
  748.     }
  749. }
  750.  
  751. sub PROCESS_DATE {
  752.     @command = split(" ",@_[0]);
  753.     $key = pop(@command);
  754.     #print "key=$key\n";
  755.  
  756.     $limit = $default_limit;
  757.     if ($key =~ /^limit$/i) {
  758.         $limit = pop(@command);
  759.         $limit = int($limit);
  760.         if ($limit < 0) {$limit = 0;}
  761.         if ($limit == "no") {$limit = -1;}
  762.         $key = pop(@command);
  763.         }
  764.     if ($key =~ /^gophergmt$/i) {
  765.         return(@command[0],$limit);
  766.     } elsif ($key =~ /^ago$/i){
  767.     local($time) = time();
  768.     LOOP1: while(@command) {
  769.         $num = shift(@command);
  770.         $unit = shift(@command);
  771.         #print ">2>",substr($unit,0,1),"<2<\n";
  772.         $factor = @seconds{substr($unit,0,1)};
  773.         if (! $factor) {
  774.             &LOG_MESS("Don't know unit \"$unit\"");
  775.             die("Don't know unit \"$unit\"");
  776.             }
  777.         $time = $time - $num * $factor;
  778.         #print ">3>$time<3<\n";
  779.         }
  780.     if ($time < 0) {$time = 0;}
  781.     local($gopht) = &TIME2GOPHER($time,1);
  782.     #print ">4>$gopht<4<\n";
  783.     return ($gopht,$limit);
  784.     }
  785.     else {
  786.         &LOG_MESS("I don't know time format \"$key\"");
  787.         die("I don't know time format \"$key\"");
  788.         }
  789.  }
  790.     
  791. sub TIME2GOPHER {
  792.     #print "time=$time\n";
  793.     local($time,$gmt_p) = @_;
  794.         local($cursec,$curmin,$curhour,$curmday,$curmon,$curyear,
  795.           $curwday,$curyday,$curisdst);
  796.     if ($gmt_p) {
  797.          ($cursec,$curmin,$curhour,$curmday,$curmon,$curyear,
  798.           $curwday,$curyday,$curisdst)
  799.             = gmtime($time);
  800.        } else {
  801.          ($cursec,$curmin,$curhour,$curmday,$curmon,$curyear,
  802.           $curwday,$curyday,$curisdst)
  803.             = localtime($time);
  804.         }
  805.     #print ">1>",gmtime(time),"<1<\n";
  806.     $curmon++;
  807.      if ($curyear >= 70) {$curyear = $curyear + 1900;}
  808.           elsif ($curyear < 20) {$curyear = $curyear + 2000;}
  809.           else {die "Sorry, limited to years between 1970 and 2020";}
  810.      return(sprintf("%.4d%.2d%.2d%.2d%.2d%.2d",$curyear,$curmon,
  811.                       $curmday,$curhour,$curmin,$cursec));
  812.         }
  813.  
  814. sub LASTDATE {
  815.     local($short_name) = @_;
  816.     open(OLD,"<$whatsnewdir/$short_name.$oldext") || die "$whatsnewdir/$short_name.$oldext: ",$!;
  817.     local($filelen) = (stat(OLD))[7];
  818.     local($date) = &RETURN_TIME($filelen-3);
  819.     return($date);
  820. }
  821.  
  822. sub LOG_MESS {
  823.     local($date) = `date`;
  824.     chop($date);
  825.     $date =~ s/[^ ]* ([0-9]+)$/$1/;# remove timezone info
  826.     open(LOG,">>$logfile")||die("$logfile: $!");
  827.     flock(LOG,2);
  828.     seek(LOG,0,2); # in case someone appended while we waited
  829.     print LOG "$date $$ $theirname : @_[0]\n";
  830.     flock(LOG,8);
  831.     close(LOG);
  832.     }
  833.  
  834. sub PRINT_EXTRA {
  835.     local($short_name,$path1,$host1,$port1,$date,$star_p,$include) =@_;
  836.     #print "datebpe=$date\n";
  837.  
  838.     if (index($include,"about") >= $[){
  839.     if ($star_p) {print "0**";} else {print "0";}
  840.     print "About \"What's New in $short_name?\"    0/$short_name    $host    $myport\r\n";}
  841.  
  842.     if (index($include,"whatsnew") >= $[){
  843.     if ($star_p) {print "7**";} else {print "7";}
  844.     print "What's New in $short_name? (e.g.: 1 day 2 hours ago)    7/$short_name    $host    $myport\r\n";}
  845.  
  846.     if (index($include,"top") >= $[){
  847.     if ($star_p) {print "1**";} else {print "1";}
  848.     print "Top of \"$short_name\"    $path1    $host1    $port1\r\n";}
  849.  
  850.     if (index($include,"bookmark") >= $[){ 
  851.     if ($star_p) {print "1**";} else {print "1";}
  852.     print "Bookmark for future new \"$short_name\" items ($date gophergmt)    1/$short_name/$date gophergmt    $host    $myport\r\n";}
  853.     }
  854.  
  855. sub GET_GOPHER_REF {
  856.     local($short_name) = @_;
  857.     #print $short_name,"\n";
  858.     #print "$whatsnewdir/$short_name.$saveext\n";
  859.     if (! open(SAVE,"<$whatsnewdir/$short_name.$saveext")) {
  860.            &LOG_MESS("Can't find file $whatsnewdir/$short_name.$saveext");
  861.                die $!;
  862.      }
  863.     <SAVE>;
  864.     $path1 = <SAVE>; chop($path1);
  865.     $host1 = <SAVE>; chop($host1);
  866.     $port1 = <SAVE>; chop($port1);
  867.     close(SAVE);
  868.     #print "$path1,$host1,$port1\n";
  869.     return($path1,$host1,$port1);
  870.     }
  871.